package com.gitee.tje.waving.aop;

import com.gitee.tje.waving.exception.WavingException;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;

/**
 * @author taojiaen
 * @date 2018-02-19 22:23
 */
public class MethodInvoker{

    public Map<Method, PriorityQueue<MethodHook>> methodMap = new HashMap<>();
    private final TargetSource target;

    public MethodInvoker(TargetSource targetSource) {
        this.target = targetSource;
        processMethodHook();
    }


    public Object invoke(Method method, Object[] params) {
        boolean invokePlain =
                (methodMap.get(method) == null || methodMap.get(method).isEmpty());
        if (invokePlain) {
            try {
                return method.invoke(target, params);
            } catch (IllegalAccessException e) {
                throw new WavingException(e);
            } catch (InvocationTargetException e) {
                if (e.getCause() != null
                        && e.getCause() instanceof RuntimeException) {
                    throw (RuntimeException) e.getCause();
                } else {
                    throw new AopExeption(e);
                }
            }
        }


        List<MethodHook> methodInterceptorList = new ArrayList<>(methodMap.get(method));
        MethodProcess process = new MethodProcess(method, params, target.target, methodInterceptorList);
        return process.process();
    }

    private void processMethodHook() {
        for (Map.Entry<Method, List<MethodHook>> methodListEntry : target.methodListMap.entrySet()) {
           methodMap.putIfAbsent(methodListEntry.getKey(), new PriorityQueue<>(methodHookComparable));
           methodMap.get(methodListEntry.getKey()).addAll(methodListEntry.getValue());
        }
    }


    private static Comparator<MethodHook> methodHookComparable = (o1, o2) -> {
        if (o1.getOrder() < o2.getOrder()) {
            return -1;
        }
        if (o1.getOrder() > o2.getOrder()) {
            return 1;
        }
        return 0;
    };
}
